<?php

/**
 * @package php-font-lib
 * @link    http://php-font-lib.googlecode.com/
 * @author  Fabien M�nager <fabien.menager@gmail.com>
 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
 * @version $Id: font_table_cmap.cls.php 40 2012-01-22 21:48:41Z fabien.menager $
 */

/**
 * `cmap` font table.
 * 
 * @package php-font-lib
 */
class Font_Table_cmap extends Font_Table {

    private static $header_format = array(
        "version" => self::uint16,
        "numberSubtables" => self::uint16,
    );
    private static $subtable_header_format = array(
        "platformID" => self::uint16,
        "platformSpecificID" => self::uint16,
        "offset" => self::uint32,
    );
    private static $subtable_v4_format = array(
        "length" => self::uint16,
        "language" => self::uint16,
        "segCountX2" => self::uint16,
        "searchRange" => self::uint16,
        "entrySelector" => self::uint16,
        "rangeShift" => self::uint16,
    );

    protected function _parse() {
        $font = $this->getFont();

        $cmap_offset = $font->pos();

        $data = $font->unpack(self::$header_format);

        $subtables = array();
        for ($i = 0; $i < $data["numberSubtables"]; $i++) {
            $subtables[] = $font->unpack(self::$subtable_header_format);
        }
        $data["subtables"] = $subtables;

        foreach ($data["subtables"] as $i => &$subtable) {
            $font->seek($cmap_offset + $subtable["offset"]);

            $subtable["format"] = $font->readUInt16();

            // @todo Only CMAP version 4
            if ($subtable["format"] != 4) {
                unset($data["subtables"][$i]);
                $data["numberSubtables"] --;
                continue;
            }

            $subtable += $font->unpack(self::$subtable_v4_format);
            $segCount = $subtable["segCountX2"] / 2;
            $subtable["segCount"] = $segCount;

            $endCode = $font->r(array(self::uint16, $segCount));

            $font->readUInt16(); // reservedPad

            $startCode = $font->r(array(self::uint16, $segCount));
            $idDelta = $font->r(array(self::int16, $segCount));

            $ro_start = $font->pos();
            $idRangeOffset = $font->r(array(self::uint16, $segCount));

            $glyphIndexArray = array();
            for ($i = 0; $i < $segCount; $i++) {
                $c1 = $startCode[$i];
                $c2 = $endCode[$i];
                $d = $idDelta[$i];
                $ro = $idRangeOffset[$i];

                if ($ro > 0)
                    $font->seek($subtable["offset"] + 2 * $i + $ro);

                for ($c = $c1; $c <= $c2; $c++) {
                    if ($ro == 0)
                        $gid = ($c + $d) & 0xFFFF;
                    else {
                        $offset = ($c - $c1) * 2 + $ro;
                        $offset = $ro_start + 2 * $i + $offset;

                        $font->seek($offset);
                        $gid = $font->readUInt16();

                        if ($gid != 0)
                            $gid = ($gid + $d) & 0xFFFF;
                    }

                    if ($gid > 0) {
                        $glyphIndexArray[$c] = $gid;
                    }
                }
            }

            $subtable += array(
                "endCode" => $endCode,
                "startCode" => $startCode,
                "idDelta" => $idDelta,
                "idRangeOffset" => $idRangeOffset,
                "glyphIndexArray" => $glyphIndexArray,
            );
        }

        $this->data = $data;
    }

    function _encode() {
        $font = $this->getFont();
        $subset = $font->getSubset();

        $segments = array();

        $i = count($segments) - 1;
        $j = $i + 1;
        $prevCode = 0;
        $glyphIndexArray = array();

        foreach ($subset as $code => $gid) {
            if ($prevCode + 1 != $code) {
                $i++;
                $segments[$i] = array();
            }

            $segments[$i][] = array($code, $j);

            $glyphIndexArray[] = $code;
            $j++;
            $prevCode = $code;
        }

        $segments[][] = array(0xFFFF, 0xFFFF);

        $startCode = array();
        $endCode = array();
        $idDelta = array();

        foreach ($segments as $codes) {
            $start = reset($codes);
            $end = end($codes);

            $startCode[] = $start[0];
            $endCode[] = $end[0];
            $idDelta[] = $start[1] - $start[0];
        }

        $segCount = count($startCode);
        $idRangeOffset = array_fill(0, $segCount, 0);

        $searchRange = 1;
        $entrySelector = 0;
        while ($searchRange * 2 <= $segCount) {
            $searchRange *= 2;
            $entrySelector++;
        }
        $searchRange *= 2;
        $rangeShift = $segCount * 2 - $searchRange;

        $subtables = array(
            array(
                // header
                "platformID" => 3, // Unicode
                "platformSpecificID" => 1,
                "offset" => null,
                // subtable
                "format" => 4,
                "length" => null,
                "language" => 0,
                "segCount" => $segCount,
                "segCountX2" => $segCount * 2,
                "searchRange" => $searchRange,
                "entrySelector" => $entrySelector,
                "rangeShift" => $rangeShift,
                "startCode" => $startCode,
                "endCode" => $endCode,
                "idDelta" => $idDelta,
                "idRangeOffset" => $idRangeOffset,
                "glyphIndexArray" => $glyphIndexArray,
            )
        );

        $data = array(
            "version" => 0,
            "numberSubtables" => count($subtables),
            "subtables" => $subtables,
        );

        $length = $font->pack(self::$header_format, $data);

        $subtable_headers_size = $data["numberSubtables"] * 8; // size of self::$subtable_header_format
        $subtable_headers_offset = $font->pos();

        $length += $font->write(str_repeat("\0", $subtable_headers_size), $subtable_headers_size);

        // write subtables data
        foreach ($data["subtables"] as $i => $subtable) {
            $length_before = $length;
            $data["subtables"][$i]["offset"] = $length;

            $length += $font->writeUInt16($subtable["format"]);

            $before_subheader = $font->pos();
            $length += $font->pack(self::$subtable_v4_format, $subtable);

            $segCount = $subtable["segCount"];
            $length += $font->w(array(self::uint16, $segCount), $subtable["endCode"]);
            $length += $font->writeUInt16(0); // reservedPad
            $length += $font->w(array(self::uint16, $segCount), $subtable["startCode"]);
            $length += $font->w(array(self::int16, $segCount), $subtable["idDelta"]);
            $length += $font->w(array(self::uint16, $segCount), $subtable["idRangeOffset"]);
            $length += $font->w(array(self::uint16, $segCount), $subtable["glyphIndexArray"]);

            $after_subtable = $font->pos();

            $subtable["length"] = $length - $length_before;
            $font->seek($before_subheader);
            $length += $font->pack(self::$subtable_v4_format, $subtable);

            $font->seek($after_subtable);
        }

        // write subtables headers
        $font->seek($subtable_headers_offset);
        foreach ($data["subtables"] as $subtable) {
            $font->pack(self::$subtable_header_format, $subtable);
        }

        return $length;
    }

}
